//---------------------------------------------------------------------
// <copyright file="MetadataHelpers.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//---------------------------------------------------------------------

namespace System.Data.Common.Utils
{
    using System.Diagnostics;
    using System.Data.Metadata.Edm;
    using System.Collections.Generic;

    /// <summary>
    /// Helper functions to get metadata information.
    /// </summary>
    internal static class MetadataHelpers
    {
        //
        // Facet Helpers
        //
        #region Facet Helpers

        /// <summary>
        /// Searches for Facet Description with the name specified. 
        /// </summary>
        /// <param name="facetCollection">Collection of facet description</param>
        /// <param name="facetName">name of the facet</param>
        /// <returns></returns>
        internal static FacetDescription GetFacet(IEnumerable<FacetDescription> facetCollection, string facetName)
        {
            foreach (FacetDescription facetDescription in facetCollection)
            {
                if (facetDescription.FacetName == facetName)
                {
                    return facetDescription;
                }
            }

            return null;
        }

        internal static bool TryGetBooleanFacetValue(TypeUsage type, string facetName, out bool boolValue)
        {
            boolValue = false;
            Facet boolFacet;
            if (type.Facets.TryGetValue(facetName, false, out boolFacet) && boolFacet.Value != null)
            {
                boolValue = (bool)boolFacet.Value;
                return true;
            }

            return false;
        }

        internal static bool TryGetByteFacetValue(TypeUsage type, string facetName, out byte byteValue)
        {
            byteValue = 0;
            Facet byteFacet;
            if (type.Facets.TryGetValue(facetName, false, out byteFacet) && byteFacet.Value != null && !byteFacet.IsUnbounded)
            {
                byteValue = (byte)byteFacet.Value;
                return true;
            }

            return false;
        }

        internal static bool TryGetIntFacetValue(TypeUsage type, string facetName, out int intValue)
        {
            intValue = 0;
            Facet intFacet;
            if (type.Facets.TryGetValue(facetName, false, out intFacet) && intFacet.Value != null && !intFacet.IsUnbounded)
            {
                intValue = (int)intFacet.Value;
                return true;
            }

            return false;
        }

        internal static bool TryGetIsFixedLength(TypeUsage type, out bool isFixedLength)
        {
            if (!IsPrimitiveType(type, PrimitiveTypeKind.String) &&
                !IsPrimitiveType(type, PrimitiveTypeKind.Binary))
            {
                isFixedLength = false;
                return false;
            }

            // Binary and String MaxLength facets share the same name
            return TryGetBooleanFacetValue(type, FixedLengthFacetName, out isFixedLength);
        }

        internal static bool TryGetIsUnicode(TypeUsage type, out bool isUnicode)
        {
            if (!IsPrimitiveType(type, PrimitiveTypeKind.String))
            {
                isUnicode = false;
                return false;
            }

            return TryGetBooleanFacetValue(type, MetadataHelpers.UnicodeFacetName, out isUnicode);
        }

        internal static bool IsFacetValueConstant(TypeUsage type, string facetName)
        {
            // Binary and String FixedLength facets share the same name
            return GetFacet(((PrimitiveType)type.EdmType).FacetDescriptions, facetName).IsConstant;
        }

        internal static bool TryGetMaxLength(TypeUsage type, out int maxLength)
        {
            if (!IsPrimitiveType(type, PrimitiveTypeKind.String) &&
                !IsPrimitiveType(type, PrimitiveTypeKind.Binary))
            {
                maxLength = 0;
                return false;
            }

            // Binary and String FixedLength facets share the same name
            return MetadataHelpers.TryGetIntFacetValue(type, MaxLengthFacetName, out maxLength);
        }

        internal static bool TryGetPrecision(TypeUsage type, out byte precision)
        {
            if (!IsPrimitiveType(type, PrimitiveTypeKind.Decimal))
            {
                precision = 0;
                return false;
            }

            return MetadataHelpers.TryGetByteFacetValue(type, MetadataHelpers.PrecisionFacetName, out precision);
        }

        internal static bool TryGetScale(TypeUsage type, out byte scale)
        {
            if (!IsPrimitiveType(type, PrimitiveTypeKind.Decimal))
            {
                scale = 0;
                return false;
            }

            return MetadataHelpers.TryGetByteFacetValue(type, ScaleFacetName, out scale);
        }

        #endregion

        //
        // Facet Names
        //
        #region Facet Names

        /// <summary>
        /// Name of the MaxLength Facet
        /// </summary>
        internal const string MaxLengthFacetName = "MaxLength";

        /// <summary>
        /// Name of the Unicode Facet
        /// </summary>
        internal const string UnicodeFacetName = "Unicode";

        /// <summary>
        /// Name of the FixedLength Facet
        /// </summary>
        internal const string FixedLengthFacetName = "FixedLength";

        /// <summary>
        /// Name of the Precision Facet
        /// </summary>
        internal const string PrecisionFacetName = "Precision";

        /// <summary>
        /// Name of the Scale Facet
        /// </summary>
        internal const string ScaleFacetName = "Scale";

        /// <summary>
        /// Name of the Nullable Facet
        /// </summary>
        internal const string NullableFacetName = "Nullable";

        /// <summary>
        /// Name of the DefaultValue Facet
        /// </summary>
        internal const string DefaultValueFacetName = "DefaultValue";

        #endregion

        //
        // Type Semantics
        //
        #region Type Semantics

        internal static bool TryGetPrimitiveTypeKind(TypeUsage type, out PrimitiveTypeKind typeKind)
        {
            if (type != null && type.EdmType != null && type.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType)
            {
                typeKind = ((PrimitiveType)type.EdmType).PrimitiveTypeKind;
                return true;
            }

            typeKind = default(PrimitiveTypeKind);
            return false;
        }

        internal static PrimitiveTypeKind GetPrimitiveTypeKind(TypeUsage typeUsage)
        {
            Debug.Assert(null != typeUsage && null != typeUsage.EdmType && typeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType);

            PrimitiveType primitiveType = (PrimitiveType)typeUsage.EdmType;

            return primitiveType.PrimitiveTypeKind;
        }

        internal static bool IsPrimitiveType(EdmType type)
        {
            return (BuiltInTypeKind.PrimitiveType == type.BuiltInTypeKind);
        }

        internal static bool IsPrimitiveType(TypeUsage type, PrimitiveTypeKind primitiveTypeKind)
        {
            PrimitiveTypeKind typeKind;
            if (MetadataHelpers.TryGetPrimitiveTypeKind(type, out typeKind))
            {
                return (typeKind == primitiveTypeKind);
            }
            return false;
        }

        internal static bool IsNullable(TypeUsage type)
        {
            Facet nullableFacet;
            if (type.Facets.TryGetValue(NullableFacetName, false, out nullableFacet))
            {
                return (bool)nullableFacet.Value;
            }
            return true;
        }

        internal static bool IsReferenceType(GlobalItem item)
        {
            return (BuiltInTypeKind.RefType == item.BuiltInTypeKind);
        }

        internal static bool IsRowType(GlobalItem item)
        {
            return (BuiltInTypeKind.RowType == item.BuiltInTypeKind);
        }

        internal static bool IsCollectionType(GlobalItem item)
        {
            return (BuiltInTypeKind.CollectionType == item.BuiltInTypeKind);
        }

        internal static TypeUsage GetElementTypeUsage(TypeUsage type)
        {
            if (IsCollectionType(type.EdmType))
            {
                return ((CollectionType)type.EdmType).TypeUsage;
            }
            else if (IsReferenceType(type.EdmType))
            {
                return TypeUsage.CreateDefaultTypeUsage(((RefType)type.EdmType).ElementType);
            }

            return null;
        }

        internal static TEdmType GetEdmType<TEdmType>(TypeUsage typeUsage)
            where TEdmType : EdmType
        {
            return (TEdmType)typeUsage.EdmType;
        }

        internal static bool IsCanonicalFunction(EdmFunction function)
        {
            return function.NamespaceName.Equals(EdmNamespaceName, StringComparison.InvariantCulture);
        }

        internal static IList<EdmProperty> GetProperties(TypeUsage typeUsage)
        {
            return GetProperties(typeUsage.EdmType);
        }

        internal static IList<EdmProperty> GetProperties(EdmType edmType)
        {
            switch (edmType.BuiltInTypeKind)
            {
                case BuiltInTypeKind.ComplexType:
                    return ((ComplexType)edmType).Properties;
                case BuiltInTypeKind.EntityType:
                    return ((EntityType)edmType).Properties;
                case BuiltInTypeKind.RowType:
                    return ((RowType)edmType).Properties;
                default:
                    return new List<EdmProperty>();
            }
        }

        #endregion

        //
        // Metadata Properties
        //
        #region Metadata Properties

        internal static T GetMetadataProperty<T>(MetadataItem item, string propertyName)
        {
            MetadataProperty property;
            if (!item.MetadataProperties.TryGetValue(propertyName, true, out property)
                || !(property.Value is T))
            {
                return default(T);
            }

            return (T)property.Value;
        }

        internal const string TableMetadata = "Table";

        internal const string SchemaMetadata = "Schema";

        internal const string DefiningQueryMetadata = "DefiningQuery";

        internal const string CommandTextMetadata = "CommandTextAttribute";

        internal const string StoreFunctionNameMetadata = "StoreFunctionNameAttribute";

        internal const string BuiltInMetadata = "BuiltInAttribute";

        internal const string NiladicFunctionMetadata = "NiladicFunctionAttribute";

        internal const string OracleCursorParameterNameMetadata = "EFOracleProviderExtensions:CursorParameterName";

        #endregion

        //
        // Misc Metadata Helpers
        //
        #region Misc Metadata Helpers

        internal static ParameterDirection ParameterModeToParameterDirection(ParameterMode mode)
        {
            switch (mode)
            {
                case ParameterMode.In:
                    return ParameterDirection.Input;

                case ParameterMode.InOut:
                    return ParameterDirection.InputOutput;

                case ParameterMode.Out:
                    return ParameterDirection.Output;

                case ParameterMode.ReturnValue:
                    return ParameterDirection.ReturnValue;

                default:
                    Debug.Fail("unrecognized mode " + mode.ToString());
                    return default(ParameterDirection);
            }
        }

        #endregion

        //
        // Misc Metadata Contstans
        //
        #region Misc Metadata Constants

        /// <summary>
        /// The Edm namespace name, used for canonical functions
        /// </summary>
        internal const string EdmNamespaceName = "Edm";

        #endregion
    }
}
